// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

/*
 * Author: Constantin Jucovschi (c.jucovschi@jacobs-university.de)
 * Licence: MIT
 */

(function(mod) {
	if (typeof exports == "object" && typeof module == "object") // CommonJS
		mod(require("../../lib/codemirror"));
	else if (typeof define == "function" && define.amd) // AMD
		define(["../../lib/codemirror"], mod);
	else // Plain browser env
		mod(CodeMirror);
})(function(CodeMirror) {
	"use strict";

	CodeMirror.defineMode("stex", function() {
		"use strict";

		function pushCommand(state, command) {
			state.cmdState.push(command);
		}

		function peekCommand(state) {
			if (state.cmdState.length > 0) {
				return state.cmdState[state.cmdState.length - 1];
			} else {
				return null;
			}
		}

		function popCommand(state) {
			var plug = state.cmdState.pop();
			if (plug) {
				plug.closeBracket();
			}
		}

		// returns the non-default plugin closest to the end of the list
		function getMostPowerful(state) {
			var context = state.cmdState;
			for (var i = context.length - 1; i >= 0; i--) {
				var plug = context[i];
				if (plug.name == "DEFAULT") {
					continue;
				}
				return plug;
			}
			return { styleIdentifier: function() { return null; } };
		}

		function addPluginPattern(pluginName, cmdStyle, styles) {
			return function () {
				this.name = pluginName;
				this.bracketNo = 0;
				this.style = cmdStyle;
				this.styles = styles;
				this.argument = null;   // \begin and \end have arguments that follow. These are stored in the plugin

				this.styleIdentifier = function() {
					return this.styles[this.bracketNo - 1] || null;
				};
				this.openBracket = function() {
					this.bracketNo++;
					return "bracket";
				};
				this.closeBracket = function() {};
			};
		}

		var plugins = {};

		plugins["importmodule"] = addPluginPattern("importmodule", "tag", ["string", "builtin"]);
		plugins["documentclass"] = addPluginPattern("documentclass", "tag", ["", "atom"]);
		plugins["usepackage"] = addPluginPattern("usepackage", "tag", ["atom"]);
		plugins["begin"] = addPluginPattern("begin", "tag", ["atom"]);
		plugins["end"] = addPluginPattern("end", "tag", ["atom"]);

		plugins["DEFAULT"] = function () {
			this.name = "DEFAULT";
			this.style = "tag";

			this.styleIdentifier = this.openBracket = this.closeBracket = function() {};
		};

		function setState(state, f) {
			state.f = f;
		}

		// called when in a normal (no environment) context
		function normal(source, state) {
			var plug;
			// Do we look like '\command' ?  If so, attempt to apply the plugin 'command'
			if (source.match(/^\\[a-zA-Z@]+/)) {
				var cmdName = source.current().slice(1);
				plug = plugins[cmdName] || plugins["DEFAULT"];
				plug = new plug();
				pushCommand(state, plug);
				setState(state, beginParams);
				return plug.style;
			}

			// escape characters
			if (source.match(/^\\[$&%#{}_]/)) {
				return "tag";
			}

			// white space control characters
			if (source.match(/^\\[,;!\/\\]/)) {
				return "tag";
			}

			// find if we're starting various math modes
			if (source.match("\\[")) {
				setState(state, function(source, state){ return inMathMode(source, state, "\\]"); });
				return "keyword";
			}
			if (source.match("$$")) {
				setState(state, function(source, state){ return inMathMode(source, state, "$$"); });
				return "keyword";
			}
			if (source.match("$")) {
				setState(state, function(source, state){ return inMathMode(source, state, "$"); });
				return "keyword";
			}

			var ch = source.next();
			if (ch == "%") {
				// special case: % at end of its own line; stay in same state
				if (!source.eol()) {
					setState(state, inCComment);
				}
				return "comment";
			}
			else if (ch == '}' || ch == ']') {
				plug = peekCommand(state);
				if (plug) {
					plug.closeBracket(ch);
					setState(state, beginParams);
				} else {
					return "error";
				}
				return "bracket";
			} else if (ch == '{' || ch == '[') {
				plug = plugins["DEFAULT"];
				plug = new plug();
				pushCommand(state, plug);
				return "bracket";
			}
			else if (/\d/.test(ch)) {
				source.eatWhile(/[\w.%]/);
				return "atom";
			}
			else {
				source.eatWhile(/[\w\-_]/);
				plug = getMostPowerful(state);
				if (plug.name == 'begin') {
					plug.argument = source.current();
				}
				return plug.styleIdentifier();
			}
		}

		function inCComment(source, state) {
			source.skipToEnd();
			setState(state, normal);
			return "comment";
		}

		function inMathMode(source, state, endModeSeq) {
			if (source.eatSpace()) {
				return null;
			}
			if (source.match(endModeSeq)) {
				setState(state, normal);
				return "keyword";
			}
			if (source.match(/^\\[a-zA-Z@]+/)) {
				return "tag";
			}
			if (source.match(/^[a-zA-Z]+/)) {
				return "variable-2";
			}
			// escape characters
			if (source.match(/^\\[$&%#{}_]/)) {
				return "tag";
			}
			// white space control characters
			if (source.match(/^\\[,;!\/]/)) {
				return "tag";
			}
			// special math-mode characters
			if (source.match(/^[\^_&]/)) {
				return "tag";
			}
			// non-special characters
			if (source.match(/^[+\-<>|=,\/@!*:;'"`~#?]/)) {
				return null;
			}
			if (source.match(/^(\d+\.\d*|\d*\.\d+|\d+)/)) {
				return "number";
			}
			var ch = source.next();
			if (ch == "{" || ch == "}" || ch == "[" || ch == "]" || ch == "(" || ch == ")") {
				return "bracket";
			}

			// eat comments here, because inCComment returns us to normal state!
			if (ch == "%") {
				if (!source.eol()) {
					source.skipToEnd();
				}
				return "comment";
			}
			return "error";
		}

		function beginParams(source, state) {
			var ch = source.peek(), lastPlug;
			if (ch == '{' || ch == '[') {
				lastPlug = peekCommand(state);
				lastPlug.openBracket(ch);
				source.eat(ch);
				setState(state, normal);
				return "bracket";
			}
			if (/[ \t\r]/.test(ch)) {
				source.eat(ch);
				return null;
			}
			setState(state, normal);
			popCommand(state);

			return normal(source, state);
		}

		return {
			startState: function() {
				return {
					cmdState: [],
					f: normal
				};
			},
			copyState: function(s) {
				return {
					cmdState: s.cmdState.slice(),
					f: s.f
				};
			},
			token: function(stream, state) {
				return state.f(stream, state);
			}
		};
	});

	CodeMirror.defineMIME("text/x-stex", "stex");
	CodeMirror.defineMIME("text/x-latex-plugin", "stex");

});
